﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Dynamic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Noria.Web.MyMSDN.Models;
using Noria.Web.Utility;
using File = Noria.Web.MyMSDN.Models.File;
using MmContext = Noria.Web.MyMSDN.Models.MmContext;

namespace Noria.Web.MyMSDN.Workers
{
    internal class MsdnWorker : IWorker
    {
        private static readonly log4net.ILog slog = log4net.LogManager.GetLogger("Noria.Spider");
        private static readonly log4net.ILog alog = log4net.LogManager.GetLogger("Noria.Audit");
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private static readonly string rootMSDN = (string)NConfig.Root.spider.settings.msdn;

        private static readonly string urlCategory = rootMSDN + "/en-us/subscriptions/json/GetProductCategories?brand=MSDN&localeCode=en-us";
        private static readonly string urlFamily = rootMSDN + "/en-us/subscriptions/json/GetProductFamiliesForCategory?brand=MSDN&categoryId={0}";
        private static readonly string urlSearch = rootMSDN + "/en-us/subscriptions/json/GetFileSearchResult";


        private readonly IStatistics stat;
        private readonly AsyncWaiter waiter;

        private dynamic root;

        private readonly MsdnCategoryWorker categoryWoker = new MsdnCategoryWorker(urlCategory);
        private readonly MsdnFamilyWorker familyWoker = new MsdnFamilyWorker(urlFamily);
        private readonly MsdnFileWorker fileWoker = new MsdnFileWorker(urlSearch);

        private MsdnWorker(IStatistics stat, AsyncWaiter waiter)
        {
            this.stat = stat;
            this.waiter = waiter;
            root = new ExpandoObject();
        }

        public static IWorker Create(IStatistics stat, AsyncWaiter waiter)
        {
            return new MsdnWorker(stat, waiter);
        }

        public void Work()
        {
            if (waiter.IsAlive)
                GetCategory();

            if (waiter.IsAlive)
                GetFamily();

            if (waiter.IsAlive)
                GetFile();

            if (!waiter.IsAlive)
                stat.Cancel();
        }

        internal void GetFile(bool test = false)
        {
            if (test)
            {
                using (var db = new MmContext())
                {
                    // 'Operating Systems'
                    var tmp = db.Categories.Where(p => p.CategoryId == 36).Include("Families").ToArray();
                    root.Categories = tmp;

                    // 'Windows 7'
                    tmp.First().Families = tmp.First().Families.Where(p => p.FamilyId == 350).ToList();
                }
            }

            var cats = root.Categories as IEnumerable;
            foreach (Category cat in cats)
            {
                // check live
                if (!waiter.IsAlive)
                    return;

                Parallel.ForEach(cat.Families, (family, state) =>
                    {
                        // check live
                        if (!waiter.IsAlive)
                            state.Break();

                        try
                        {
                            fileWoker.RetrieveFileListByFamily(stat, waiter, family);
                        }
                        catch (Exception e)
                        {
                            slog.Error("Failed to retrieve file list by family " + family.Name, e);
                            stat.AddError();
                        }
                    });
            }

            // join all the files into one
            var files = new List<File>();
            foreach (Category cat in cats)
            {
                foreach (var family in cat.Families)
                {
                    files.AddRange(family.Files ?? new List<File>());
                }
            }

            // remove duplicated files - -||| Dynamics NAV 4.0, Dynamics NAV 5.0
            log.Debug("Before files.DistinctBy...");
            files = files.DistinctBy(p => p.FileId).ToList();
            log.Debug("After files.DistinctBy...");

            fileWoker.Save(stat, files);
        }

        internal void GetFamily(bool test = false)
        {
            if (test)
            {
                using (var db = new MmContext())
                {
                    // 'Designer Tools'
                    root.Categories = db.Categories.Where(p => p.CategoryId == 62).ToArray();
                }
            }

            var cats = root.Categories as IEnumerable;
            Parallel.ForEach((IEnumerable<Category>)cats, (cat, state) =>
                {
                    // check alive
                    if (!waiter.IsAlive)
                        state.Break();

                    try
                    {
                        familyWoker.RetrieveFamilyListByCategory(stat, waiter, cat);
                    }
                    catch (Exception e)
                    {
                        slog.Error("Failed to retrieve family list by category " + cat.Name, e);
                        stat.AddError();
                    }
                });

            // join all the families into one
            var families = new List<Family>();
            foreach (Category cat in cats)
            {
                families.AddRange(cat.Families ?? new List<Family>());
            }

            // diff with db, update if necessary
            familyWoker.Save(stat, families);
        }

        internal void GetCategory(bool test = false)
        {
            var cats = categoryWoker.RetrieveCategoryList(stat, waiter);
            root.Categories = cats;

            categoryWoker.Save(stat, cats);
        }

    }
}